#include "halley/audio/audio_event.h"
#include "audio_engine.h"
#include "halley/audio/audio_clip.h"
#include "halley/resources/resource_data.h"
#include "halley/bytes/byte_serializer.h"
#include "halley/file_formats/config_file.h"
#include "halley/text/string_converter.h"
#include "halley/resources/resources.h"
#include "audio_filter_resample.h"
#include "halley/audio/audio_object.h"
#include "halley/file_formats/yaml_convert.h"
#include "halley/support/logger.h"

using namespace Halley;

AudioEvent::AudioEvent() = default;

AudioEvent::AudioEvent(const AudioEvent& other)
{
	*this = other;
}

AudioEvent& AudioEvent::operator=(const AudioEvent& other)
{
	// eh, will do
	auto bytes = Serializer::toBytes(other);
	auto s = Deserializer(bytes);
	deserialize(s);
	setAssetId(other.getAssetId());
	return *this;
}

AudioEvent::AudioEvent(const ConfigNode& config)
{
	if (config.hasKey("actions")) {
		for (auto& actionNode: config["actions"]) {
			const auto type = fromString<AudioEventActionType>(actionNode["type"].asString());
			if (auto action = makeAction(type); action) {
				action->load(actionNode);
				actions.push_back(std::move(action));
			}
		}
	}
}

Vector<AudioObject> AudioEvent::convertLegacy(const String& audioEventId, ConfigNode& node)
{
	Vector<AudioObject> output;

	if (node.hasKey("actions")) {
		for (auto& actionNode: node["actions"]) {
			const auto type = fromString<AudioEventActionType>(actionNode["type"].asString());
			if (type == AudioEventActionType::Play) {
				if (!actionNode.hasKey("object")) {
					// Legacy play
					AudioObject obj;
					obj.loadLegacyEvent(actionNode);
					obj.setAssetId(output.empty() ? audioEventId : audioEventId + "_" + toString(output.size()));

					actionNode.asMap().clear();
					actionNode["type"] = "play";
					actionNode["object"] = obj.getAssetId();

					output.push_back(obj);
				}
			}
		}
	}

	return output;
}

void AudioEvent::collectVariablesUsed(Vector<String>& variables, Vector<String>& switches) const
{
	for (const auto& action: actions) {
		action->collectVariablesUsed(variables, switches);
	}
}

String AudioEvent::toYAML() const
{
	auto actionsNode = ConfigNode::SequenceType();
	for (const auto& action: actions) {
		actionsNode.push_back(action->toConfigNode());
	}
	
	ConfigNode result = ConfigNode::MapType();
	result["actions"] = std::move(actionsNode);

	YAMLConvert::EmitOptions options;
	return YAMLConvert::generateYAML(result, options);
}

size_t AudioEvent::run(AudioEngine& engine, AudioEventId id, AudioEmitter& globalEmitter, AudioEmitter& objectEmitter) const
{
	size_t nEmitters = 0;
	for (const auto& a: actions) {
		if (a->getScope() == AudioEventScope::Object && &globalEmitter == &objectEmitter) {
			Logger::logWarning("AudioEvent " + getAssetId() + " is trying to play an object scope action, but no audio source was provided (missing AudioSourceComponent?).");
		}

		if (a->run(engine, id, a->getScope() == AudioEventScope::Object ? objectEmitter : globalEmitter)) {
			++nEmitters;
		}
	}
	return nEmitters;
}

const Vector<std::unique_ptr<AudioEventAction>>& AudioEvent::getActions() const
{
	return actions;
}

Vector<std::unique_ptr<AudioEventAction>>& AudioEvent::getActions()
{
	return actions;
}

void AudioEvent::serialize(Serializer& s) const
{
	s << uint32_t(actions.size());
	for (auto& a: actions) {
		s << toString(a->getType());
		s << *a;
	}
}

void AudioEvent::deserialize(Deserializer& s)
{
	actions.clear();
	
	uint32_t size;
	s >> size;
	for (uint32_t i = 0; i < size; ++i) {
		String name;
		s >> name;
		auto type = fromString<AudioEventActionType>(name);

		if (std::unique_ptr<AudioEventAction> newAction = makeAction(type); newAction) {
			s >> *newAction;
			actions.push_back(std::move(newAction));
		}
	}
}

void AudioEvent::reload(Resource&& resource)
{
	*this = std::move(dynamic_cast<AudioEvent&>(resource));
}

std::shared_ptr<AudioEvent> AudioEvent::loadResource(ResourceLoader& loader)
{
	auto staticData = loader.getStatic(false);
	if (!staticData) {
		return {};
	}
	
	Deserializer s(staticData->getSpan());
	auto event = std::make_shared<AudioEvent>();
	s >> *event;

	event->setAssetId(loader.getName());
	event->loadDependencies(loader.getResources());
	return event;
}

void AudioEvent::makeDefault()
{
	*this = AudioEvent();
}

void AudioEvent::parseYAML(gsl::span<const std::byte> yaml)
{
	ConfigFile config;
	YAMLConvert::parseConfig(config, yaml);
	*this = AudioEvent(config.getRoot());
}

void AudioEvent::loadDependencies(Resources& resources)
{
	for (auto& a: actions) {
		a->loadDependencies(*this, resources);
	}
}

std::unique_ptr<AudioEventAction> AudioEvent::makeAction(AudioEventActionType type)
{
	switch (type) {
	case AudioEventActionType::Play:
		return std::make_unique<AudioEventActionPlay>();
	case AudioEventActionType::Stop:
		return std::make_unique<AudioEventActionStop>();
	case AudioEventActionType::Pause:
		return std::make_unique<AudioEventActionPause>();
	case AudioEventActionType::Resume:
		return std::make_unique<AudioEventActionResume>();
	case AudioEventActionType::StopBus:
		return std::make_unique<AudioEventActionStopBus>();
	case AudioEventActionType::PauseBus:
		return std::make_unique<AudioEventActionPauseBus>();
	case AudioEventActionType::ResumeBus:
		return std::make_unique<AudioEventActionResumeBus>();
	case AudioEventActionType::SetBusVolume:
		return std::make_unique<AudioEventActionSetBusVolume>();
	case AudioEventActionType::SetVolume:
		return std::make_unique<AudioEventActionSetVolume>();
	case AudioEventActionType::SetSwitch:
		return std::make_unique<AudioEventActionSetSwitch>();
	case AudioEventActionType::CopySwitch:
		return std::make_unique<AudioEventActionCopySwitch>();
	case AudioEventActionType::SetVariable:
		return std::make_unique<AudioEventActionSetVariable>();
	}
	return {};
}

String AudioEvent::getActionName(AudioEventActionType type)
{
	switch (type) {
	case AudioEventActionType::Play:
		return "Play";
	case AudioEventActionType::Stop:
		return "Stop";
	case AudioEventActionType::Pause:
		return "Pause";
	case AudioEventActionType::Resume:
		return "Resume";
	case AudioEventActionType::StopBus:
		return "Stop Bus";
	case AudioEventActionType::PauseBus:
		return "Pause Bus";
	case AudioEventActionType::ResumeBus:
		return "Resume Bus";
	case AudioEventActionType::SetBusVolume:
		return "Set Bus Volume";
	case AudioEventActionType::SetSwitch:
		return "Set Switch";
	case AudioEventActionType::CopySwitch:
		return "Copy Switch";
	case AudioEventActionType::SetVariable:
		return "Set Variable";
	case AudioEventActionType::SetVolume:
		return "Set Volume";
	}
	return "Unknown";
}

void AudioEventAction::serialize(Serializer& s) const
{
	s << scope;
}

void AudioEventAction::deserialize(Deserializer& s)
{
	s >> scope;
}

void AudioEventAction::load(const ConfigNode& config)
{
	scope = fromString<AudioEventScope>(config["scope"].asString("object"));
}

ConfigNode AudioEventAction::toConfigNode() const
{
	ConfigNode::MapType result;
	result["type"] = toString(getType());
	result["scope"] = toString(scope);
	return result;
}

void AudioEventActionObject::loadObject(const ConfigNode& node, bool requiresObject)
{
	AudioEventAction::load(node);
	if (requiresObject) {
		objectName = node["object"].asString();
	} else {
		objectName = node["object"].asString("");
	}

	if (node.hasKey("fade")) {
		fade = AudioFade(node["fade"]);
	}
}

void AudioEventActionObject::serialize(Serializer& s) const
{
	AudioEventAction::serialize(s);
	s << objectName;
	s << fade;
}

void AudioEventActionObject::deserialize(Deserializer& s)
{
	AudioEventAction::deserialize(s);
	s >> objectName;
	s >> fade;
}

ConfigNode AudioEventActionObject::toConfigNode() const
{
	auto result = AudioEventAction::toConfigNode();

	if (!objectName.isEmpty()) {
		result["object"] = objectName;
	}
	if (fade.hasFade()) {
		result["fade"] = fade.toConfigNode();
	}

	return result;
}

const String& AudioEventActionObject::getObjectName() const
{
	return objectName;
}

void AudioEventActionObject::setObjectName(const String& name, const AudioEvent& event, Resources& resources)
{
	objectName = name;
	loadDependencies(event, resources);
}

void AudioEventActionObject::setObjectName(const String& name)
{
	objectName = name;
}

const AudioFade& AudioEventActionObject::getFade() const
{
	return fade;
}

AudioFade& AudioEventActionObject::getFade()
{
	return fade;
}

void AudioEventActionObject::loadDependencies(const AudioEvent& event, Resources& resources)
{
	object = {};
	objectId = {};

	if (!objectName.isEmpty()) {
		object = resources.tryGet<AudioObject>(objectName);
		if (object) {
			objectId = object->getAudioObjectId();
		} else {
			Logger::logError("Error when loading dependencies for AudioEvent \"" + event.getAssetId() + "\": AudioObject not found: " + objectName);
		}
	}
}

void AudioEventActionObject::collectVariablesUsed(Vector<String>& variables, Vector<String>& switches) const
{
	if (object) {
		object->collectVariablesUsed(variables, switches);
	}
}

void AudioEventActionBus::load(const ConfigNode& config)
{
	AudioEventAction::load(config);
	busName = config["busName"].asString("");
	if (config.hasKey("fade")) {
		fade = AudioFade(config["fade"]);
	}
}

ConfigNode AudioEventActionBus::toConfigNode() const
{
	auto result = AudioEventAction::toConfigNode();
	result["busName"] = busName;
	if (fade.hasFade()) {
		result["fade"] = fade.toConfigNode();
	}
	return result;
}

void AudioEventActionBus::serialize(Serializer& s) const
{
	AudioEventAction::serialize(s);
	s << busName;
	s << fade;
}

void AudioEventActionBus::deserialize(Deserializer& s)
{
	AudioEventAction::deserialize(s);
	s >> busName;
	s >> fade;
}

const String& AudioEventActionBus::getBusName() const
{
	return busName;
}

void AudioEventActionBus::setBusName(String name)
{
	busName = std::move(name);
}

AudioFade& AudioEventActionBus::getFade()
{
	return fade;
}

AudioEventScope AudioEventActionBus::getScope() const
{
	return AudioEventScope::Global;
}


AudioEventActionPlay::AudioEventActionPlay()
	: playGain(1, 1)
	, playPitch(1, 1)
{
}

void AudioEventActionPlay::load(const ConfigNode& node)
{
	loadObject(node);
	
	if (node.hasKey("object")) {
		playGain = node["gain"].asFloatRange(Range<float>(1, 1));
		playPitch = node["pitch"].asFloatRange(Range<float>(1, 1));
		singleton = node["singleton"].asBool(false);
	}

	delay = node["delay"].asFloat(0);
}

bool AudioEventActionPlay::run(AudioEngine& engine, AudioEventId uniqueId, AudioEmitter& emitter) const
{
	if (!objectId) {
		return false;
	}

	if (!object) {
		Logger::logError("AudioEvent Play action has an object id assigned, but no object reference");
		return false;
	}

	if (singleton) {
		const size_t nPlaying = emitter.forVoices(*objectId, [&] (AudioVoice&) {});
		if (nPlaying > 0) {
			return false;
		}
	}

	const auto delaySamples = std::lroundf(delay * static_cast<float>(AudioConfig::sampleRate));
	if (auto voice = engine.makeObjectVoice(*object, uniqueId, emitter, playGain, playPitch, delaySamples)) {
		voice->play(fade);
		emitter.addVoice(std::move(voice));
		return true;
	}
	return false;
}

AudioEventActionType AudioEventActionPlay::getType() const
{
	return AudioEventActionType::Play;
}

float AudioEventActionPlay::getDelay() const
{
	return delay;
}

void AudioEventActionPlay::setDelay(float delay)
{
	this->delay = delay;
}

Range<float> AudioEventActionPlay::getGain() const
{
	return playGain;
}

Range<float>& AudioEventActionPlay::getGain()
{
	return playGain;
}

void AudioEventActionPlay::setGain(Range<float> gain)
{
	playGain = gain;
}

Range<float> AudioEventActionPlay::getPitch() const
{
	return playPitch;
}

Range<float>& AudioEventActionPlay::getPitch()
{
	return playPitch;
}

void AudioEventActionPlay::setPitch(Range<float> pitch)
{
	playPitch = pitch;
}

bool AudioEventActionPlay::isSingleton() const
{
	return singleton;
}

void AudioEventActionPlay::setSingleton(bool value)
{
	singleton = value;
}

void AudioEventActionPlay::serialize(Serializer& s) const
{
	s << singleton;
	s << playGain;
	s << playPitch;
	s << delay;
	AudioEventActionObject::serialize(s);
}

void AudioEventActionPlay::deserialize(Deserializer& s)
{
	s >> singleton;
	s >> playGain;
	s >> playPitch;
	s >> delay;
	AudioEventActionObject::deserialize(s);
}

ConfigNode AudioEventActionPlay::toConfigNode() const
{
	auto result = AudioEventActionObject::toConfigNode();

	if (delay > 0.0001f) {
		result["delay"] = delay;
	}
	if (singleton) {
		result["singleton"] = singleton;
	}

	if (std::abs(playGain.start - 1.0f) > 0.0001f && std::abs(playGain.end - 1.0f) > 0.0001f) {
		result["gain"] = playGain;
	}
	if (std::abs(playPitch.start - 1.0f) > 0.0001f && std::abs(playPitch.end - 1.0f) > 0.0001f) {
		result["pitch"] = playPitch;
	}
	
	return result;
}

void AudioEventActionStop::load(const ConfigNode& config)
{
	loadObject(config);
}

bool AudioEventActionStop::run(AudioEngine& engine, AudioEventId id, AudioEmitter& emitter) const
{
	if (!objectId) {
		return false;
	}
	
	emitter.forVoices(*objectId, [&] (AudioVoice& voice)
	{
		voice.stop(fade);
	});

	return false;
}

void AudioEventActionPause::load(const ConfigNode& config)
{
	loadObject(config);
}

bool AudioEventActionPause::run(AudioEngine& engine, AudioEventId id, AudioEmitter& emitter) const
{
	if (!objectId) {
		return false;
	}
	
	emitter.forVoices(*objectId, [&] (AudioVoice& voice)
	{
		voice.pause(fade);
	});

	return true;
}

void AudioEventActionResume::load(const ConfigNode& config)
{
	loadObject(config);
	force = config["force"].asBool(false);
}

bool AudioEventActionResume::run(AudioEngine& engine, AudioEventId id, AudioEmitter& emitter) const
{
	if (!objectId) {
		return false;
	}
	
	emitter.forVoices(*objectId, [&] (AudioVoice& voice)
	{
		voice.resume(fade, force);
	});

	return true;
}

bool AudioEventActionResume::getForce() const
{
	return force;
}

void AudioEventActionResume::setForce(bool force)
{
	this->force = force;
}

void AudioEventActionResume::serialize(Serializer& s) const
{
	AudioEventActionObject::serialize(s);
	s << force;
}

void AudioEventActionResume::deserialize(Deserializer& s)
{
	AudioEventActionObject::deserialize(s);
	s >> force;
}

ConfigNode AudioEventActionResume::toConfigNode() const
{
	auto result = AudioEventActionObject::toConfigNode();
	result["force"] = force;
	return result;
}

void AudioEventActionStopBus::load(const ConfigNode& config)
{
	AudioEventActionBus::load(config);
}

bool AudioEventActionStopBus::run(AudioEngine& engine, AudioEventId id, AudioEmitter& emitter) const
{
	if (busName.isEmpty()) {
		return false;
	}

	Vector<int> ids;
	engine.getBusIds(busName, ids);
	for (const auto id: ids) {
		engine.forVoicesOnBus(id, [&] (AudioVoice& voice)
		{
			voice.stop(fade);
		});
	}

	return false;
}

void AudioEventActionPauseBus::load(const ConfigNode& config)
{
	AudioEventActionBus::load(config);
}

bool AudioEventActionPauseBus::run(AudioEngine& engine, AudioEventId id, AudioEmitter& emitter) const
{
	if (busName.isEmpty()) {
		return false;
	}

	Vector<int> ids;
	engine.getBusIds(busName, ids);
	for (const auto id: ids) {
		engine.forVoicesOnBus(id, [&] (AudioVoice& voice)
		{
			voice.pause(fade);
		});
	}

	return true;
}

void AudioEventActionResumeBus::load(const ConfigNode& config)
{
	AudioEventActionBus::load(config);
	force = config["force"].asBool(false);
}

bool AudioEventActionResumeBus::run(AudioEngine& engine, AudioEventId id, AudioEmitter& emitter) const
{
	if (busName.isEmpty()) {
		return false;
	}

	Vector<int> ids;
	engine.getBusIds(busName, ids);
	for (const auto id: ids) {
		engine.forVoicesOnBus(id, [&] (AudioVoice& voice)
		{
			voice.resume(fade, force);
		});
	}

	return true;
}

bool AudioEventActionResumeBus::getForce() const
{
	return force;
}

void AudioEventActionResumeBus::setForce(bool force)
{
	this->force = force;
}

void AudioEventActionResumeBus::serialize(Serializer& s) const
{
	AudioEventActionBus::serialize(s);
	s << force;
}

void AudioEventActionResumeBus::deserialize(Deserializer& s)
{
	AudioEventActionBus::deserialize(s);
	s >> force;
}

ConfigNode AudioEventActionResumeBus::toConfigNode() const
{
	auto result = AudioEventActionBus::toConfigNode();
	result["force"] = force;
	return result;
}



void AudioEventActionSetBusVolume::load(const ConfigNode& config)
{
	AudioEventActionBus::load(config);
	gain = config["gain"].asFloat(0.0f);
	volumeName = config["volumeName"].asString("");
}

ConfigNode AudioEventActionSetBusVolume::toConfigNode() const
{
	auto result = AudioEventActionBus::toConfigNode();
	result["gain"] = gain;
	result["volumeName"] = volumeName;
	return result;
}

bool AudioEventActionSetBusVolume::run(AudioEngine& engine, AudioEventId id, AudioEmitter& emitter) const
{
	if (busName.isEmpty()) {
		return false;
	}

	return engine.setBusGain(busName, gain, fade, volumeName);
}

float AudioEventActionSetBusVolume::getGain() const
{
	return gain;
}

void AudioEventActionSetBusVolume::setGain(float value)
{
	gain = value;
}

const String& AudioEventActionSetBusVolume::getVolumeName() const
{
	return volumeName;
}

void AudioEventActionSetBusVolume::setVolumeName(String name)
{
	volumeName = std::move(name);
}

void AudioEventActionSetBusVolume::serialize(Serializer& s) const
{
	AudioEventActionBus::serialize(s);
	s << gain;
	s << volumeName;
}

void AudioEventActionSetBusVolume::deserialize(Deserializer& s)
{
	AudioEventActionBus::deserialize(s);
	s >> gain;
	s >> volumeName;
}



void AudioEventActionSetVolume::load(const ConfigNode& config)
{
	loadObject(config);
	gain = config["gain"].asFloat(1.0f);
}

ConfigNode AudioEventActionSetVolume::toConfigNode() const
{
	auto result = AudioEventActionObject::toConfigNode();
	result["gain"] = gain;
	return result;
}

bool AudioEventActionSetVolume::run(AudioEngine& engine, AudioEventId id, AudioEmitter& emitter) const
{
	if (!objectId) {
		return false;
	}
	
	emitter.forVoices(*objectId, [&] (AudioVoice& voice)
	{
		voice.setUserGain(gain);
	});

	return true;
}

float AudioEventActionSetVolume::getGain() const
{
	return gain;
}

void AudioEventActionSetVolume::setGain(float value)
{
	gain = value;
}

void AudioEventActionSetVolume::serialize(Serializer& s) const
{
	AudioEventActionObject::serialize(s);
	s << gain;
}

void AudioEventActionSetVolume::deserialize(Deserializer& s)
{
	AudioEventActionObject::deserialize(s);
	s >> gain;
}

void AudioEventActionSetSwitch::load(const ConfigNode& config)
{
	AudioEventAction::load(config);
	switchId = config["switchId"].asString();
	value = config["value"].asString();
	scope = fromString<AudioEventScope>(config["scope"].asString("object"));
}

ConfigNode AudioEventActionSetSwitch::toConfigNode() const
{
	auto result = AudioEventAction::toConfigNode();
	result["switchId"] = switchId;
	result["value"] = value;
	return result;
}

bool AudioEventActionSetSwitch::run(AudioEngine& engine, AudioEventId id, AudioEmitter& emitter) const
{
	emitter.setSwitchValue(switchId, value);

	return true;
}

const String& AudioEventActionSetSwitch::getSwitchId() const
{
	return switchId;
}

const String& AudioEventActionSetSwitch::getValue() const
{
	return value;
}

void AudioEventActionSetSwitch::setSwitchId(String id)
{
	switchId = std::move(id);
}

void AudioEventActionSetSwitch::setValue(String val)
{
	value = std::move(val);
}

void AudioEventActionSetSwitch::serialize(Serializer& s) const
{
	AudioEventAction::serialize(s);
	s << switchId;
	s << value;
}

void AudioEventActionSetSwitch::deserialize(Deserializer& s)
{
	AudioEventAction::deserialize(s);
	s >> switchId;
	s >> value;
}

void AudioEventActionCopySwitch::load(const ConfigNode& config)
{
	AudioEventAction::load(config);
	dstSwitchId = config["dstSwitchId"].asString();
	srcSwitchId = config["srcSwitchId"].asString();
	scope = fromString<AudioEventScope>(config["scope"].asString("object"));
}

ConfigNode AudioEventActionCopySwitch::toConfigNode() const
{
	auto result = AudioEventAction::toConfigNode();
	result["dstSwitchId"] = dstSwitchId;
	result["srcSwitchId"] = srcSwitchId;
	return result;
}

bool AudioEventActionCopySwitch::run(AudioEngine& engine, AudioEventId id, AudioEmitter& emitter) const
{
	emitter.setSwitchValue(dstSwitchId, emitter.getSwitchValue(srcSwitchId));

	return true;
}

const String& AudioEventActionCopySwitch::getDstSwitchId() const
{
	return dstSwitchId;
}

const String& AudioEventActionCopySwitch::getSrcSwitchId() const
{
	return srcSwitchId;
}

void AudioEventActionCopySwitch::setDstSwitchId(String id)
{
	dstSwitchId = id;
}

void AudioEventActionCopySwitch::setSrcSwitchId(String id)
{
	srcSwitchId = id;
}

void AudioEventActionCopySwitch::serialize(Serializer& s) const
{
	AudioEventAction::serialize(s);
	s << dstSwitchId;
	s << srcSwitchId;
}

void AudioEventActionCopySwitch::deserialize(Deserializer& s)
{
	AudioEventAction::deserialize(s);
	s >> dstSwitchId;
	s >> srcSwitchId;
}

void AudioEventActionSetVariable::load(const ConfigNode& config)
{
	AudioEventAction::load(config);
	variableId = config["variableId"].asString();
	value = config["value"].asFloat();
}

ConfigNode AudioEventActionSetVariable::toConfigNode() const
{
	auto result = AudioEventAction::toConfigNode();
	result["variableId"] = variableId;
	result["value"] = value;
	return result;
}

bool AudioEventActionSetVariable::run(AudioEngine& engine, AudioEventId id, AudioEmitter& emitter) const
{
	emitter.setVariableValue(variableId, value);

	return true;
}

const String& AudioEventActionSetVariable::getVariableId() const
{
	return variableId;
}

float AudioEventActionSetVariable::getValue() const
{
	return value;
}

void AudioEventActionSetVariable::setVariableId(String id)
{
	variableId = std::move(id);
}

void AudioEventActionSetVariable::setValue(float val)
{
	value = val;
}

void AudioEventActionSetVariable::serialize(Serializer& s) const
{
	AudioEventAction::serialize(s);
	s << variableId;
	s << value;
}

void AudioEventActionSetVariable::deserialize(Deserializer& s)
{
	AudioEventAction::deserialize(s);
	s >> variableId;
	s >> value;
}
